import os
import re
import json
import pandas as pd
from langchain.indexes import graph
from langchain_openai import ChatOpenAI, AzureChatOpenAI
from langchain_deepseek import ChatDeepSeek
from typing import Annotated
from langchain_community.utilities import BingSearchAPIWrapper
from langchain_community.tools import TavilySearchResults, BingSearchResults
from langchain_core.messages import ToolMessage
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import ToolNode
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

# question_exp = '''OLED中出现短时残影和长时残影的原因分别是什么？
# '''
# answer_exp = '''OLED（有机发光二极管）显示技术是一种自发光的显示技术，具有高对比度、宽色域、快速响应、薄型化和柔性等优点。然而，OLED显示器也存在一些问题，其中之一就是残影现象。残影可以分为短时残影和长时残影，它们的原因有所不同。

# 短时残影的原因主要是由于OLED像素的响应速度和驱动电路的特性。当屏幕上显示的图像发生变化时，像素需要一定的时间来完成亮度的调整。在这个过程中，像素的亮度不能立即达到目标值，导致在图像切换的瞬间出现短暂的残影。此外，OLED显示器的驱动电路可能会受到噪声干扰，这也可能导致短时残影的出现。

# 长时残影的原因主要是由于OLED材料的特性和老化过程。OLED显示器中的有机发光材料在长时间使用过程中会发生老化，导致其发光效率降低。当像素长时间显示高亮度图像时，有机发光材料的损伤会更加严重，从而导致像素的亮度无法恢复到原始状态，形成长时残影。此外，OLED显示器中的像素是独立发光的，当某个像素出现老化问题时，其周围的像素也会受到影响，导致整个区域的亮度不均匀，形成残影。

# 为了减少残影现象，可以采取以下措施：

# 1.优化驱动电路：通过降低噪声干扰、提高驱动精度等方式，减少短时残影的出现。

# 2.提高OLED材料的稳定性：研发具有更高稳定性和更长寿命的有机发光材料，降低长时残影的发生概率。

# 3.采用像素补偿技术：通过检测像素的老化程度，对像素的亮度进行调整，以补偿亮度衰减，减少残影现象。

# 4.合理控制显示内容：避免长时间显示高亮度图像，减少像素的老化程度，从而降低长时残影的发生概率。

# 总之，OLED显示技术中的残影现象是由多种因素引起的，包括像素响应速度、驱动电路特性、OLED材料的老化过程等。通过优化驱动电路、提高OLED材料稳定性、采用像素补偿技术和合理控制显示内容等措施，可以有效地减少残影现象。

# '''

os.environ["OPENAI_API_KEY"] = "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"
os.environ["AZURE_OPENAI_API_KEY"] = "98ff3b4afac846a7bede351bcec20ce8"
os.environ["AZURE_OPENAI_ENDPOINT"] = "https://tcl-ai-france.openai.azure.com/"
os.environ["TAVILY_API_KEY"] = "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"
# os.environ["BING_SUBSCRIPTION_KEY"] = "1b74855d50be4fdaaaa2850d10a33e05"
gpt4 = ChatOpenAI(model="gpt-4")
azure_gpt4 = AzureChatOpenAI(
    azure_deployment="gpt-4-0314",
    api_version="2023-12-01-preview",
    temperature=0,
    max_tokens=None,
    timeout=None,
    max_retries=3
)
gpt4o = ChatOpenAI(model="gpt-4o")
deepseek_v3 = ChatDeepSeek(
    model="deepseek-ai/DeepSeek-V3",
    api_key="sk-wrhzrmvzwcdhwjujhncmkexgghwngtatcwruhxyiemopuvcx",
    api_base="https://api.siliconflow.cn/v1"
)
deepseek_r1 = ChatDeepSeek(
    model="deepseek-ai/DeepSeek-R1",
    api_key="sk-wrhzrmvzwcdhwjujhncmkexgghwngtatcwruhxyiemopuvcx",
    api_base="https://api.siliconflow.cn/v1"
)
models = {
    "openai_old": gpt4,
    "openai": gpt4o,
    "ms_gpt4": azure_gpt4,
    "deepseek-v3": deepseek_v3,
    "deepseek-r1": deepseek_r1
}


def get_text(file):
    with open(file=file, mode='r', encoding='utf-8') as obj:
        data = obj.read()
    return data


def get_task_and_human_score(file):
    content = pd.read_excel(file, sheet_name='Sheet1')
    question_dict, score_dict = {}, {}
    for i in range(0, content.shape[0]):
        line = content.iloc[i,]
        question = line['Question']
        answer = line['Answer']
        if question not in question_dict.keys():
            question_dict[question] = answer
            score_dict[question] = [int(line['准确性人工评分']), int(line['全面性人工评分']),
                                    int(line['实用性人工评分'])]
        else:
            print('Find dup task {}, check the table!'.format(question))
            exit()
    return question_dict, score_dict


# question_text = get_text('question_example.txt')
# anwer_text = get_text('answer_example.txt')
# expert_prompt_1 = '''现在有一个针对显示半导体领域的问题“{}”的回答，回答的具体内容如下：
# {}

# 现在请你以一位具有丰富显示半导体领域理论和实践经验的专家身份从准确性、全面性和实用性三个角度对给出的回答进行评分。
# 准确性需要通过回答是否解决了提出的问题、回答中对专业名词的解释是否准确以及回答中是否存在误导信息三个方面进行判断，分数由高到低分为4挡，3分代表非常准确，2分代表基本准确，1分代表不太准确，0分代表很不准确；
# 全面性需要通过回答是否详细解释了问题中的关键概念以及回答是否对问题中涉及的技术进行了准确的扩展描述两个方面进行判断，分数由高到低分为4挡，3分代表非常全面，2分代表比较全面，1分代表不太全面，0分代表很不全面；
# 实用性评分需要通过判断回答中的解题思路或解决问题的办法是否能够进行实际操作验证来进行，分数由高到低分为4挡，3分代表非常实用，2分代表比较实用，1分代表不太实用，0分代表很不实用。

# 你给出的评分要以json格式输出，json表格中只有“准确性”、“全面性”、“实用性”三个属性，每个属性只能填0-3的整数中的一个。
# 请记住，只输出评分结果即可，不需要解释。
# '''

expert_prompt_2 = '''你是一位具有丰富显示半导体领域理论和实践经验的专家。
之前你的助手整理了一份对这一领域的问题“{}”的高质量回答，其内容如下：
{}

现在有另一份质量未知的针对前面问题的回答如下：
{}

请你把助手提供的回答作为标准答案，然后从准确性、全面性和实用性三个角度对质量未知的回答进行评分。

准确性分数由高到低分为4挡，3分代表非常准确，2分代表基本准确，1分代表不太准确，0分代表很不准确，你要采用扣分制来评分，比对标准答案，如果回答中的内容出现了事实错误则扣除1分；如果回答中的专业名词解释错误则扣除1分；如果回答没有完全解答问题也需要扣除1分。
全面性需要通过回答是否详细解释了问题中的关键概念以及回答是否对问题中涉及的技术进行了准确的扩展描述两个方面进行判断，分数由高到低分为4挡，3分代表非常全面，2分代表比较全面，1分代表不太全面，0分代表很不全面；
实用性评分需要通过判断回答中的解题思路或解决问题的办法是否能够进行实际操作验证来进行，分数由高到低分为4挡，3分代表非常实用，2分代表比较实用，1分代表不太实用，0分代表很不实用。

你给出的评分要以json格式输出，json表格中只有“准确性”、“全面性”、“实用性”三个属性，每个属性只能填0-3的整数中的一个。
请记住，只输出评分结果即可，不需要解释。
'''

assistant_run_tool_prompt = '''有一段针对显示半导体领域的问题“{}”的回答，具体内容如下：
{}

请你从准确性、全面性和实用性三个角度分析回答对问题的解答质量，然后设计出合适的中文关键词，然后用这些关键词一次性搜索，找到有效的信息帮助进一步评估回答的质量。
准确性高的回答不仅正确解答了提出的问题，同时对涉及的专业名词有准确的解释，还不存在任何误导信息。
全面性高的回答会详细解释问题涉及到的关键概念，还会对问题设计的技术方向进行扩展描述。
实用性高的回答里的内容和方法具有很高的可操作性，提问者可根据回答去复现和验证。
'''

assistant_summary_prompt = '''下面是涉及显示半导体领域问题“{}”的，从网络上收集到的一些信息：
{}

请你归纳总结这些信息，力求准确、全面地解答上面的问题，除了解答问题你的回答还需要把网络信息的重点都囊括进去，同时对专业名词进行必要的解释，并且对问题、回答涉及到的技术进行相应的扩展论述。
'''

# 定义工具和使用工具的agent
web_search = TavilySearchResults(max_results=5)
# bing_search = BingSearchAPIWrapper(k=5)
# web_search = BingSearchResults(num_results=5, api_wrapper=bing_search)
tools = [web_search]
tools_by_name = {tool.name: tool for tool in tools}
# agent_with_tools = gpt4.bind_tools(tools)
agent_with_tools = azure_gpt4.bind_tools(tools)
# agent_with_tools = gpt4o.bind_tools(tools)
# agent_with_tools = deepseek_v3.bind_tools(tools)
# agent_with_tools = deepseek_r1.bind_tools(tools)

# 创建 checkpointer
# 这里保存在内存，也可改为数据库
memory = MemorySaver()


class State(TypedDict):
    """
    定义一个字典类型 State（继承自 TypeDict）
    包含一个键 messages
    值是一个 list，并且列表的更新方式由 add_messages 函数定义
    add_message 将新消息追加到列表中，而不是覆盖原有列表
    """
    messages: Annotated[list, add_messages]
    question: str
    answer: str


# 评测Agent
def oled_expert(state: State):
    messages = state["messages"]
    question = state["question"]
    answer = state["answer"]
    query = expert_prompt_2.format(question, messages[-1].content, answer)
    # response = gpt4.invoke(query)
    response = azure_gpt4.invoke(query)
    # response = gpt4o.invoke(query)
    # response = deepseek_v3.invoke(query)
    # response = deepseek_r1.invoke(query)
    # print('Expert: \n{}'.format(response.content))
    # print('\n')
    return {"messages": [response], "question": question, "answer": answer}


# 提供参考信息的Agent
def search_assistant(state: State):
    # messages = state["messages"]
    question = state["question"]
    answer = state["answer"]
    query = assistant_run_tool_prompt.format(question, answer)
    response = agent_with_tools.invoke(query)
    # print('Assistant1: \n{}'.format(response.content))
    # print('\n')
    return {"messages": [response], "question": question, "answer": answer}


# 总结网络信息输出标准回答的Agent
def summary_assistant(state: State):
    messages = state["messages"]
    question = state["question"]
    answer = state["answer"]
    web_info = messages[-1].content
    query = assistant_summary_prompt.format(question, web_info)
    # response = gpt4.invoke(query)
    response = azure_gpt4.invoke(query)
    # response = gpt4o.invoke(query)
    # response = deepseek_v3.invoke(query)
    # response = deepseek_r1.invoke(query)
    # print('Assistant2: \n{}'.format(response.content))
    # print('\n')
    return {"messages": [response], "question": question, "answer": answer}


def run_search(state: State):
    new_messages = []
    # question = state["question"]
    # answer = state["answer"]
    # turn_num = state["turn_num"]
    tool_calls = state["messages"][-1].tool_calls
    for tool_call in tool_calls:
        tool = tools_by_name[tool_call["name"]]
        result = tool.invoke(tool_call["args"])
        new_messages.append(ToolMessage(
            {
                "content": result,
                "tool_call_id": tool_call["id"]
            })
        )
    return {"messages": new_messages}


def run_eval_exp(question,answer):
    graph_builder = StateGraph(State)

    # 2. 添加 agent node
    graph_builder.add_node("eval_agent_node", oled_expert)
    graph_builder.add_node("search_agent_node", search_assistant)
    graph_builder.add_node("summary_agent_node", summary_assistant)
    # 添加 tool 节点
    run_search = ToolNode(tools=tools)
    graph_builder.add_node("wb_search_node", run_search)
    # 2.1 添加条件边

    # 2.2 添加普通边
    graph_builder.add_edge("search_agent_node", "wb_search_node")
    graph_builder.add_edge("wb_search_node", "summary_agent_node")
    graph_builder.add_edge("summary_agent_node", "eval_agent_node")

    # 3. 定义 StateGraph 的入口
    graph_builder.add_edge(START, "search_agent_node")

    # 3. 定义 StateGraph 的出口
    graph_builder.add_edge("eval_agent_node", END)

    # 5. 创建一个 CompiledGraph，以便后续调用
    graph = graph_builder.compile(checkpointer=memory)
    ai_score_precsion, ai_score_complete, ai_score_useful = 0, 0, 0
    user_config = {"configurable": {"thread_id": '0'}}
    for event in graph.stream(
            {"messages": [''], "question": question, "answer": answer},
            user_config,
            stream_mode="updates"
        ):
            for value in event.values():
                if 'search_agent_node' in event.keys():
                    try:
                        print("Assistant1:\n{}".format(value["messages"][-1].content))
                    except:
                        pass
                elif 'summary_agent_node' in event.keys():
                    try:
                        print("Assistant2:\n{}".format(value["messages"][-1].content))
                    except:
                        pass
                elif 'eval_agent_node' in event.keys():
                    try:
                        print("Expert:\n{}".format(value["messages"][-1].content))
                    except:
                        pass
                    socre_pred_str = value["messages"][-1].content
                    socre_pred_str = re.sub(r'^```json\n', '', socre_pred_str, count=1)
                    socre_pred_str = re.sub(r'\n```$', '', socre_pred_str, count=1)
                    socre_pred = json.loads(socre_pred_str)
                    ai_score_precsion = int(socre_pred['准确性'])
                    ai_score_complete = int(socre_pred['全面性'])
                    ai_score_useful = int(socre_pred['实用性'])
                else:
                    try:
                        print("Others:\n{}".format(value["messages"][-1].content))
                    except:
                        pass
                print('\n')

    return ai_score_precsion, ai_score_complete, ai_score_useful
